#pragma once
#include <iostream>
#include <string>
#include <filesystem> //c++17中引入的文件路径相关接口头文件、
#include <fstream>    //c++的文件流
#include <memory>
#include <sstream> //像流一样把int/double等类型转成字符串
#include <unistd.h>
#include <ctime>
#include "Mutex.hpp"
using namespace std;

namespace LogModule
{
    using namespace MutexModule;

    const string gsep = "\r\n";
    // 策略模式，c++多态特性
    //  2.刷新策略 a.显示器打印 b.想指定文件写入
    //  刷新策略基类
    class LogStrategy
    {
    public:
        ~LogStrategy() = default; // 让编译器生成默认的析构函数
        // 定义日志系统中 “将日志内容（message）同步写入目标端”的规范，
        // 具体怎么写（往哪写、怎么保证同步落盘）由继承 LogStrategy 的子类
        // （如控制台日志类、文件日志类 ）去实现，让不同日志输出策略能通过多态统一调用
        virtual void SyncLog(const string &message) = 0; // 纯虚函数
    };

    // 往显示器上打印日志的策略子类
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy() {}

        // 重写虚函数
        void SyncLog(const string &message) override
        {
            // 显示器也是文件，多线程去写入时显示器就是临界资源
            // 正常把message打印到显示器上需要加锁来保护
            LockGuard lockguard(_mutex);
            cout << message << gsep;
        }

        ~ConsoleLogStrategy() {}

    private:
        Mutex _mutex;
    };

    // 文件打印日志的策略子类
    // 我们往系统的/var/log目录中创建my.log文件
    const string defaultpath = "/var/log/";
    const string defaultfile = "my.log";
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const string &path = defaultpath, const string &file = defaultfile)
            : _path(path),
              _file(file)
        {
            // 也是需要锁来进行保护的
            LockGuard lockguard(_mutex);
            if (filesystem::exists(_path)) // 判断该路径是否存在
            {
                return;
            }
            // 到这里就是该路径不存在，需要被创建
            // 当然创建路径有可能失败，所以在这做一下异常处理
            try
            {
                filesystem::create_directories(_path);
            }
            catch (const filesystem::filesystem_error &e)
            // filesystem_error处理文件系统操作错误的异常类型
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const string &message) override
        {
            LockGuard lockguard(_mutex);
            // 进行原子性的文件操作
            string filename = _path + (_path.back() != '/' ? "/" : "") + _file; //"./log/"+"my.log"
            ofstream out(filename, ios::app);                                   // 要以追加的方式打开文件
            if (!out.is_open())
            {
                return;
            }

            // 打开文件成功,开始进行写入操作
            out << message << gsep;

            out.close();
        }

        ~FileLogStrategy() {}

    private:
        string _path; // 日志文件所在路径
        string _file; // 日志文件本身
        Mutex _mutex;
    };

    // 形成完整的日志&&根据上面的策略，选择不同的刷新方式

    // 1.形成日志等级
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    // 把level转换成对应的字符串方便下面流入字符串
    string LevelToStr(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";

        default:
            return "UNKNOWN";
        }
    }

    // 获取时间
    string GetTimeStamp()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm;
        localtime_r(&curr, &curr_tm);
        char timebuffer[128]; // 设置一个128大小的缓冲区
        // 将时间格式化输入到timebuffer中
        // 这里注意的是tm_year是减去1900的，tm_month范围是0-11的
        // 所以year得加1900，月得加1
        snprintf(timebuffer, sizeof(timebuffer), "%04d-%02d-%02d %02d:%02d:%02d",
                 curr_tm.tm_year + 1900, curr_tm.tm_mon + 1, curr_tm.tm_mday,
                 curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec);

        return timebuffer;
    }

    // 1.形成日志&&2.根据不同的策略完成刷新
    class Logger
    {
    public:
        Logger()
        {
            // 先定一种默认策略
            EnableConsoleLogStrategy();
        }

        // 将刷新策略切换成往显示器上打
        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = make_unique<ConsoleLogStrategy>();
        }

        // 将刷新策略切换成往文件上打
        void EnableFileLogStrategy()
        {
            _fflush_strategy = make_unique<FileLogStrategy>();
        }

        // 定义一个内部类，表示的是未来的一条日志
        // 用内部类是为了支持运算符重载和可变参数模板
        class LogMessage
        {
        public:
            LogMessage(LogLevel &level, string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp()),
                  _level(level),
                  _pid(getpid()),
                  _src_name(src_name),
                  _line_number(line_number),
                  _logger(logger)
            {
                // 日志的左边部分合并到_loginfo中
                // 用sstream中的stringstream构建一个对象
                // 然后让不同类型的数据流入该对象中形成完整的字符串
                stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << LevelToStr(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";

                // 将ss转成字符串string类型赋给_loginfo
                _loginfo = ss.str();
            }

            // 日志的右边部分
            // 需要支持LogMessage()<<"hello world"<<3.13<<...
            // 也就是要支持连续的operator<<重载
            template <class T>
            LogMessage &operator<<(const T &info)
            {
                // 右边部分是可变的
                stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

            ~LogMessage()
            {
                // 析构时可以将一条message刷新到外部选择的策略中
                if (_logger._fflush_strategy) // 如果外部指向刷新策略的指针不为空
                {
                    // 那就是调用这个指针指向的刷新策略中的SyncLog方法进行刷新
                    _logger._fflush_strategy->SyncLog(_loginfo);
                }
            }

        private:
            string _curr_time; // 时间
            LogLevel _level;   // 日志等级
            pid_t _pid;        // 进程id
            string _src_name;  // 打印日志对应的文件名
            int _line_number;  // 行号
            string _loginfo;   // 合并之后的一条完整的信息
            Logger &_logger;   // 引用关联外部类的对象（内部类可以访问外部类的私有成员）
        };

        // 外面在写入日志时需要把相关的参数传递进来
        // 可以用仿函数的方式来访问我们的对象
        // 在外部的类中重载了一下LogMessage的()
        // 这里故意写成返回临时对象，这个对象在LogMessage内部进行operator<<重载
        // LogMessage()接收完右边可变信息完成后会被释放，然后就自动调用析构函数进行刷新了
        LogMessage operator()(LogLevel level, string name, int line)
        {
            // 返回一个临时的对象
            return LogMessage(level, name, line, *this);
        }

        ~Logger()
        {
        }

    private:
        // 需要智能指针来帮我们自动管理内存
        // 还是和Main.cc中相同，先定义基类的指针
        // 然后在不同的完成刷新策略方法中去创建智能指针对象赋给它
        unique_ptr<LogStrategy> _fflush_strategy;
    };

    // 如何调用日志：
    // 这里有全局的日志对象
    Logger logger;
    // 使用这个log对象的()重载传入需要的左边信息,连着格式化输入右边部分
    // 因为在()之后就创建返回了LogMessage的临时对象
    // 然后LogMessage对象就可以<<进行构建右边部分了
    // 构建完成返回*this指针后该临时对象会被释放，调用析构函数进行自动刷新
    // logger(LogLevel::DEBUG, "main.cc", 10) << "hello world";

    // 使用宏，简化用户操作，获取文件名和行号
    //__FILE__和__LINE__是预处理符
    // 会在编译代码时自动把我们当前文件所对应的文件名和行号传进来
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()

}
